/**
 * 
 */
package com.excalibur.core.util.qrcode;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

/**
 * @author "Jiance Qin"
 * 
 * @date 2015年5月9日
 * 
 * @time 下午12:04:09
 * 
 * @desc
 * 
 */
public class QRCoderUtil {

	protected static final Logger logger = LoggerFactory.getLogger(QRCoderUtil.class);

	public static final String DEFAULT_CHARSET = "utf-8";
	public static final String DEFAULT_QRCODE_CHARSET = "iso-8859-1";
	public static final String DEFAULT_IMAGE_MIME_TYPE = "image/png";
	public static final String DEFAULT_IMAGE_TYPE = "png";

	// qrcode img defaut config
	public static final int DEFAULT_IMAGE_WIDTH = 200;
	public static final int DEFAULT_IMAGE_HEIGHT = 200;
	public static final int DEFAULT_IMAGE_COLOR = 0xff000000;
	public static final int DEFAULT_IMAGE_BACKGROUND_COLOR = 0xffffffff;

	// inside logo img default config
	public static final int DEFAULT_LOGO_IMAGE_WIDTH = 50;
	public static final int DEFAULT_LOGO_IMAGE_HEIGHT = 50;
	public static final int DEFAULT_LOGO_FRAME_WIDTH = 2;
	public static final boolean DEFAULT_LOGO_HAS_FILLER = true;

	/**
	 * 
	 */
	private QRCoderUtil() {

	}

	/**
	 * @param textString
	 * @return
	 */
	public static byte[] QREncode(String textString) {

		return QREncode(textString, DEFAULT_CHARSET, DEFAULT_QRCODE_CHARSET, DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT,
				DEFAULT_IMAGE_TYPE);
	}

	/**
	 * @param textString
	 * @param imgWidth
	 * @param imgHeight
	 * @return
	 */
	public static byte[] QREncode(String textString, int imgWidth, int imgHeight) {

		return QREncode(textString, DEFAULT_CHARSET, DEFAULT_QRCODE_CHARSET, imgWidth, imgHeight, DEFAULT_IMAGE_TYPE);
	}

	/**
	 * @param textString
	 * @param textCharset
	 * @param qRCodeCharset
	 * @param imgWidth
	 * @param imgHeight
	 * @param imgType
	 * @return
	 */
	public static byte[] QREncode(String textString, String textCharset, String qRCodeCharset, int imgWidth,
			int imgHeight, String imgType) {

		if (textString == null || textString.isEmpty()) {
			return null;
		}
		if (textCharset == null || textCharset.isEmpty()) {
			textCharset = DEFAULT_CHARSET;
		}
		if (qRCodeCharset == null || qRCodeCharset.isEmpty()) {
			qRCodeCharset = DEFAULT_QRCODE_CHARSET;
		}
		if (imgWidth <= 0) {
			imgWidth = DEFAULT_IMAGE_WIDTH;
		}
		if (imgHeight <= 0) {
			imgHeight = DEFAULT_IMAGE_HEIGHT;
		}
		if (imgType == null || imgType.isEmpty()) {
			imgType = DEFAULT_IMAGE_TYPE;
		}

		try {
			BitMatrix byteMatrix;

			Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
			hints.put(EncodeHintType.MARGIN, 1);
			byteMatrix = new MultiFormatWriter().encode(new String(textString.getBytes(textCharset), qRCodeCharset),
					BarcodeFormat.QR_CODE, imgWidth, imgHeight, hints);

			ByteArrayOutputStream os = new ByteArrayOutputStream();

			MatrixToImageWriter.writeToStream(byteMatrix, imgType, os);
			return os.toByteArray();
		} catch (Exception e) {
			logger.error("Exception: ", e);
			return null;
		}
	}

	/**
	 * 生成二维码，同时将logo图片嵌入二维码中心，而不是覆盖在二维码之上。默认二维码宽度和高度为300px*300px，默认logo大小为80px*
	 * 80px.
	 * 
	 * @param textString
	 * @param logoImagePath
	 * @return
	 */
	public static byte[] QREncodeWithLogoInside(String textString, String logoImagePath) {

		return QREncodeWithLogoInside(textString, DEFAULT_CHARSET, DEFAULT_QRCODE_CHARSET, DEFAULT_IMAGE_TYPE,
				DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT, logoImagePath, DEFAULT_LOGO_IMAGE_WIDTH,
				DEFAULT_LOGO_IMAGE_HEIGHT, DEFAULT_LOGO_HAS_FILLER, DEFAULT_IMAGE_COLOR, DEFAULT_IMAGE_BACKGROUND_COLOR);
	}

	/**
	 * 生成二维码，同时将logo图片嵌入二维码中心，而不是覆盖在二维码之上。默认二维码宽度和高度为300px*300px，默认logo大小为80px*
	 * 80px.
	 * 
	 * @param textString
	 * @param imgWidth
	 * @param imgHeight
	 * @param logoImagePath
	 * @return
	 */
	public static byte[] QREncodeWithLogoInside(String textString, int imgWidth, int imgHeight, String logoImagePath) {

		return QREncodeWithLogoInside(textString, DEFAULT_CHARSET, DEFAULT_QRCODE_CHARSET, DEFAULT_IMAGE_TYPE,
				imgWidth, imgHeight, logoImagePath, DEFAULT_LOGO_IMAGE_WIDTH, DEFAULT_LOGO_IMAGE_HEIGHT,
				DEFAULT_LOGO_HAS_FILLER, DEFAULT_IMAGE_COLOR, DEFAULT_IMAGE_BACKGROUND_COLOR);
	}

	/**
	 * 生成二维码，同时将logo图片嵌入二维码中心，而不是覆盖在二维码之上。默认二维码宽度和高度为300px*300px，默认logo大小为80px*
	 * 80px.
	 * 
	 * @param textString
	 * @param imgWidth
	 * @param imgHeight
	 * @param logoImagePath
	 * @param logoImgWidth
	 * @param logoImgHeight
	 * @return
	 */
	public static byte[] QREncodeWithLogoInside(String textString, int imgWidth, int imgHeight, String logoImagePath,
			int logoImgWidth, int logoImgHeight) {

		return QREncodeWithLogoInside(textString, DEFAULT_CHARSET, DEFAULT_QRCODE_CHARSET, DEFAULT_IMAGE_TYPE,
				imgWidth, imgHeight, logoImagePath, logoImgWidth, logoImgHeight, DEFAULT_LOGO_HAS_FILLER,
				DEFAULT_IMAGE_COLOR, DEFAULT_IMAGE_BACKGROUND_COLOR);
	}

	/**
	 * 生成二维码，同时将logo图片嵌入二维码中心，而不是覆盖在二维码之上。默认二维码宽度和高度为300px*300px，默认logo大小为80px*
	 * 80px.
	 * 
	 * @param textString
	 * @param imgWidth
	 * @param imgHeight
	 * @param logoImagePath
	 * @param logoImgWidth
	 * @param logoImgHeight
	 * @param qRCodeImgColor
	 * @param qRCodeImgBackgroundColor
	 * @return
	 */
	public static byte[] QREncodeWithLogoInside(String textString, int imgWidth, int imgHeight, String logoImagePath,
			int logoImgWidth, int logoImgHeight, int qRCodeImgColor, int qRCodeImgBackgroundColor) {

		return QREncodeWithLogoInside(textString, DEFAULT_CHARSET, DEFAULT_QRCODE_CHARSET, DEFAULT_IMAGE_TYPE,
				imgWidth, imgHeight, logoImagePath, logoImgWidth, logoImgHeight, DEFAULT_LOGO_HAS_FILLER,
				qRCodeImgColor, qRCodeImgBackgroundColor);
	}

	/**
	 * 生成二维码，同时将logo图片嵌入二维码中心，而不是覆盖在二维码之上。
	 * 
	 * @param textString
	 *            待编码内容
	 * @param textCharset
	 *            待编码内容的字符编码
	 * @param qRCodeCharset
	 *            扫描后内容的字符编码
	 * @param imgType
	 *            二维码图片格式，jpg或png
	 * @param width
	 *            二维码宽度
	 * @param height
	 *            二维码高度
	 * @param logoImagePath
	 *            待嵌入的logo图片
	 * @param logoWidth
	 *            logo图片在二维码中心的宽度
	 * @param logoHeight
	 *            logo图片在二维码中心的高度
	 * @param hasFiller
	 *            若logo的原始宽高和所设置的logoWidth和logoHeight不成缩放比例，是否需要填充背景色
	 * @param qRCodeImgColor
	 *            二维码颜色值,32位int值，16进制表示，
	 *            0x(Alpha值)(R)(G)(B)，如0xff00ff00，注意二维码颜色必须为深色系
	 * @param qRCodeImgBackgroundColor
	 *            二维码背景色值，同二维码颜色值格式，注意背景色必须为浅色系
	 * @return
	 */
	public static byte[] QREncodeWithLogoInside(String textString, String textCharset, String qRCodeCharset,
			String imgType, int width, int height, String logoImagePath, int logoWidth, int logoHeight,
			boolean hasFiller, int qRCodeImgColor, int qRCodeImgBackgroundColor) {

		if (textString == null || textString.isEmpty()) {
			return null;
		}
		if (textCharset == null || textCharset.isEmpty()) {
			textCharset = DEFAULT_CHARSET;
		}
		if (qRCodeCharset == null || qRCodeCharset.isEmpty()) {
			qRCodeCharset = DEFAULT_QRCODE_CHARSET;
		}
		if (width <= 0) {
			width = DEFAULT_IMAGE_WIDTH;
		}
		if (height <= 0) {
			height = DEFAULT_IMAGE_HEIGHT;
		}
		if (imgType == null || imgType.isEmpty()) {
			imgType = DEFAULT_IMAGE_TYPE;
		}
		if (logoWidth <= 0) {
			logoWidth = DEFAULT_LOGO_IMAGE_WIDTH;
		}
		if (logoHeight <= 0) {
			logoHeight = DEFAULT_LOGO_IMAGE_HEIGHT;
		}

		boolean hasLogo = true;
		if (logoImagePath == null || logoImagePath.isEmpty()) {
			hasLogo = false;
		} else {
			File logoImgFile = new File(logoImagePath);
			if (!logoImgFile.exists() || !logoImgFile.canRead()) {
				hasLogo = false;
			}
		}

		if (!hasLogo) {
			return QREncode(textString, textCharset, qRCodeCharset, width, height, imgType);
		} else {
			try {
				BufferedImage bImg = genBarcodeWithLogoImg(textString, textCharset, qRCodeCharset, width, height,
						logoImagePath, logoWidth, logoHeight, hasFiller, qRCodeImgColor, qRCodeImgBackgroundColor);

				ByteArrayOutputStream os = new ByteArrayOutputStream();

				ImageIO.write(bImg, imgType, os);

				return os.toByteArray();
			} catch (Exception e) {
				logger.error("Exception: ", e);
				return null;
			}
		}
	}

	/**
	 * 生成带背景的二维码，目前不实用，背景颜色太深容易扫描不出来.
	 * 
	 * @param textString
	 * @param textCharset
	 * @param qRCodeCharset
	 * @param imgType
	 * @param width
	 * @param height
	 * @param backgroundImgPath
	 * @param qRCodeImgColor
	 * @return
	 */
	public static byte[] QREncodeWithBackgroundImg(String textString, String textCharset, String qRCodeCharset,
			String imgType, int width, int height, String backgroundImgPath, int qRCodeImgColor) {

		if (textString == null || textString.isEmpty()) {
			return null;
		}
		if (textCharset == null || textCharset.isEmpty()) {
			textCharset = DEFAULT_CHARSET;
		}
		if (qRCodeCharset == null || qRCodeCharset.isEmpty()) {
			qRCodeCharset = DEFAULT_QRCODE_CHARSET;
		}
		if (width <= 0) {
			width = DEFAULT_IMAGE_WIDTH;
		}
		if (height <= 0) {
			height = DEFAULT_IMAGE_HEIGHT;
		}
		if (imgType == null || imgType.isEmpty()) {
			imgType = DEFAULT_IMAGE_TYPE;
		}
		boolean hasBg = true;
		if (backgroundImgPath == null || backgroundImgPath.isEmpty()) {
			hasBg = false;
		} else {
			File logoImgFile = new File(backgroundImgPath);
			if (!logoImgFile.exists() || !logoImgFile.canRead()) {
				hasBg = false;
			}
		}

		if (!hasBg) {
			return QREncode(textString, textCharset, qRCodeCharset, width, height, imgType);
		} else {
			try {
				BufferedImage bImg = genBarcodeWithBackground(textString, textCharset, qRCodeCharset, width, height,
						backgroundImgPath, qRCodeImgColor);

				ByteArrayOutputStream os = new ByteArrayOutputStream();

				ImageIO.write(bImg, imgType, os);

				return os.toByteArray();
			} catch (Exception e) {
				logger.error("Exception: ", e);
				return null;
			}
		}
	}

	/**
	 * Genarate qrcode img with logo img inside.
	 * 
	 * @param content
	 * @param contentCharset
	 * @param qRCodeCharset
	 * @param width
	 * @param height
	 * @param logoImagePath
	 * @param logoWidth
	 * @param logoHeight
	 * @param hasFiller
	 * @return
	 * @throws WriterException
	 * @throws IOException
	 */
	private static BufferedImage genBarcodeWithLogoImg(String content, String contentCharset, String qRCodeCharset,
			int width, int height, String logoImagePath, int logoWidth, int logoHeight, boolean hasFiller,
			int qRCodeImgColor, int qRCodeImgBackgroundColor) throws WriterException, IOException {

		// 二维码写码器
		MultiFormatWriter mutiWriter = new MultiFormatWriter();

		// 读取logo源图像
		BufferedImage scaleImage = scale(logoImagePath, logoWidth, logoHeight, hasFiller, qRCodeImgBackgroundColor);
		int[][] srcPixels = new int[logoWidth][logoHeight];
		for (int i = 0; i < scaleImage.getWidth(); i++) {
			for (int j = 0; j < scaleImage.getHeight(); j++) {
				srcPixels[i][j] = scaleImage.getRGB(i, j);
			}
		}

		Map<EncodeHintType, Object> hint = new HashMap<EncodeHintType, Object>();
		hint.put(EncodeHintType.CHARACTER_SET, "utf-8");
		hint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
		// 生成二维码
		BitMatrix matrix = mutiWriter.encode(new String(content.getBytes(contentCharset), qRCodeCharset),
				BarcodeFormat.QR_CODE, width, height, hint);

		// 二维矩阵转为一维像素数组
		int halfW = matrix.getWidth() / 2;
		int halfH = matrix.getHeight() / 2;
		int[] pixels = new int[width * height];
		int logoImgHalfWidth = logoWidth / 2;

		for (int y = 0; y < matrix.getHeight(); y++) {
			for (int x = 0; x < matrix.getWidth(); x++) {
				// 读取logo图片
				if (x > halfW - logoImgHalfWidth && x < halfW + logoImgHalfWidth && y > halfH - logoImgHalfWidth
						&& y < halfH + logoImgHalfWidth) {
					pixels[y * width + x] = srcPixels[x - halfW + logoImgHalfWidth][y - halfH + logoImgHalfWidth];
				}
				// 在logo图片四周形成边框
				else if ((x > halfW - logoImgHalfWidth - DEFAULT_LOGO_FRAME_WIDTH
						&& x < halfW - logoImgHalfWidth + DEFAULT_LOGO_FRAME_WIDTH
						&& y > halfH - logoImgHalfWidth - DEFAULT_LOGO_FRAME_WIDTH && y < halfH + logoImgHalfWidth
						+ DEFAULT_LOGO_FRAME_WIDTH)
						|| (x > halfW + logoImgHalfWidth - DEFAULT_LOGO_FRAME_WIDTH
								&& x < halfW + logoImgHalfWidth + DEFAULT_LOGO_FRAME_WIDTH
								&& y > halfH - logoImgHalfWidth - DEFAULT_LOGO_FRAME_WIDTH && y < halfH
								+ logoImgHalfWidth + DEFAULT_LOGO_FRAME_WIDTH)
						|| (x > halfW - logoImgHalfWidth - DEFAULT_LOGO_FRAME_WIDTH
								&& x < halfW + logoImgHalfWidth + DEFAULT_LOGO_FRAME_WIDTH
								&& y > halfH - logoImgHalfWidth - DEFAULT_LOGO_FRAME_WIDTH && y < halfH
								- logoImgHalfWidth + DEFAULT_LOGO_FRAME_WIDTH)
						|| (x > halfW - logoImgHalfWidth - DEFAULT_LOGO_FRAME_WIDTH
								&& x < halfW + logoImgHalfWidth + DEFAULT_LOGO_FRAME_WIDTH
								&& y > halfH + logoImgHalfWidth - DEFAULT_LOGO_FRAME_WIDTH && y < halfH
								+ logoImgHalfWidth + DEFAULT_LOGO_FRAME_WIDTH)) {

					// 此处为logo图片与二维码的隔离边框的颜色，设置为背景色则看不出隔离边框
					pixels[y * width + x] = qRCodeImgBackgroundColor;
				} else {
					// 此处可以修改二维码的颜色，可以分别制定二维码和背景的颜色,冒号前为二维码，冒号后为背景色
					pixels[y * width + x] = matrix.get(x, y) ? qRCodeImgColor : qRCodeImgBackgroundColor;
				}
			}
		}

		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		image.getRaster().setDataElements(0, 0, width, height, pixels);

		return image;
	}

	/**
	 * 生成带背景的二维码.
	 * 
	 * @param content
	 * @param contentCharset
	 * @param qRCodeCharset
	 * @param width
	 * @param height
	 * @param logoImagePath
	 * @param qRCodeImgColor
	 * @return
	 * @throws WriterException
	 * @throws IOException
	 */
	private static BufferedImage genBarcodeWithBackground(String content, String contentCharset, String qRCodeCharset,
			int width, int height, String logoImagePath, int qRCodeImgColor) throws WriterException, IOException {

		// 二维码写码器
		MultiFormatWriter mutiWriter = new MultiFormatWriter();

		// 读取背景图像
		BufferedImage scaleImage = scale(logoImagePath, width, height, true, DEFAULT_IMAGE_BACKGROUND_COLOR);
		int[][] srcPixels = new int[width][height];
		for (int i = 0; i < scaleImage.getWidth(); i++) {
			for (int j = 0; j < scaleImage.getHeight(); j++) {
				srcPixels[i][j] = scaleImage.getRGB(i, j);
			}
		}

		Map<EncodeHintType, Object> hint = new HashMap<EncodeHintType, Object>();
		hint.put(EncodeHintType.CHARACTER_SET, "utf-8");
		hint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
		// 生成二维码
		BitMatrix matrix = mutiWriter.encode(new String(content.getBytes(contentCharset), qRCodeCharset),
				BarcodeFormat.QR_CODE, width, height, hint);

		// 二维矩阵转为一维像素数组
		int halfW = matrix.getWidth() / 2;
		int halfH = matrix.getHeight() / 2;
		int[] pixels = new int[width * height];
		int logoImgHalfWidth = width / 2;

		for (int y = 0; y < matrix.getHeight(); y++) {
			for (int x = 0; x < matrix.getWidth(); x++) {

				// 此处可以修改二维码的颜色，可以分别制定二维码和背景的颜色,冒号前为二维码，冒号后为背景色
				pixels[y * width + x] = matrix.get(x, y) ? qRCodeImgColor : srcPixels[x - halfW + logoImgHalfWidth][y
						- halfH + logoImgHalfWidth];
			}
		}

		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		image.getRaster().setDataElements(0, 0, width, height, pixels);

		return image;
	}

	/**
	 * 把传入的原始图像按高度和宽度进行缩放，生成符合要求的图标
	 * 
	 * @param srcImageFile
	 *            源文件地址
	 * @param height
	 *            目标高度
	 * @param width
	 *            目标宽度
	 * @param hasFiller
	 *            比例不对时是否需要补背景色：true为补; false为不补;
	 * @param qRCodeImgBackgroundColor
	 *            二维码背景色;
	 * @throws IOException
	 */
	private static BufferedImage scale(String srcImageFile, int height, int width, boolean hasFiller,
			int qRCodeImgBackgroundColor) throws IOException {

		double ratio = 0.0; // 缩放比例
		File file = new File(srcImageFile);
		BufferedImage srcImage = ImageIO.read(file);
		Image destImage = srcImage.getScaledInstance(width, height, BufferedImage.SCALE_SMOOTH);
		// 计算比例
		if ((srcImage.getHeight() > height) || (srcImage.getWidth() > width)) {
			if (srcImage.getHeight() > srcImage.getWidth()) {
				ratio = (new Integer(height)).doubleValue() / srcImage.getHeight();
			} else {
				ratio = (new Integer(width)).doubleValue() / srcImage.getWidth();
			}
			AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(ratio, ratio), null);
			destImage = op.filter(srcImage, null);
		}
		if (hasFiller) {// 补背景色
			BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
			Graphics2D graphic = image.createGraphics();
			graphic.setColor(Color.decode(String.valueOf(qRCodeImgBackgroundColor)));
			graphic.fillRect(0, 0, width, height);
			if (width == destImage.getWidth(null))
				graphic.drawImage(destImage, 0, (height - destImage.getHeight(null)) / 2, destImage.getWidth(null),
						destImage.getHeight(null), Color.decode(String.valueOf(qRCodeImgBackgroundColor)), null);
			else
				graphic.drawImage(destImage, (width - destImage.getWidth(null)) / 2, 0, destImage.getWidth(null),
						destImage.getHeight(null), Color.decode(String.valueOf(qRCodeImgBackgroundColor)), null);
			graphic.dispose();
			destImage = image;
		}
		return (BufferedImage) destImage;
	}
}
